/**
 * Code template comes from Eclipse plug-in "Bytecode"
 */
package com.googlecode.cswish.struts.spring.generator;

import static com.googlecode.cswish.asm.Opcodes.ACC_PUBLIC;
import static com.googlecode.cswish.asm.Opcodes.ALOAD;
import static com.googlecode.cswish.asm.Opcodes.ASTORE;
import static com.googlecode.cswish.asm.Opcodes.CHECKCAST;
import static com.googlecode.cswish.asm.Opcodes.GETFIELD;
import static com.googlecode.cswish.asm.Opcodes.INVOKESTATIC;
import static com.googlecode.cswish.asm.Opcodes.INVOKEVIRTUAL;
import static com.googlecode.cswish.asm.Opcodes.PUTFIELD;

import java.lang.reflect.Method;

import com.googlecode.cswish.asm.ClassWriter;
import com.googlecode.cswish.asm.Label;
import com.googlecode.cswish.asm.MethodVisitor;
import com.googlecode.cswish.asm.Type;
import com.googlecode.cswish.util.ClassEx;

/**	 
 * Call the real business method
 * 
 * @author Jerry.feng Date: 10/09/2008
 */
public class MethodWrapHelper {
	/**
	 * Create a method, the context as following:
	 * 
	 * <pre>
	 * public String bizMehothod() {
	 * 		// instance the biz object
	 *      Object bizInstance = GeneratorHelper.create(com.biz.class)
	 * 		this.bizMethod = bizInstance.bizMethod(this.f1, this.f2);
	 * 		return "success";
	 * }
	 * </pre>
	 * 
	 * <pre>
	 * public String bizMehothod() {
	 * 		return "success";
	 * }
	 * </pre>
	 * @param serviceType 
	 * 
	 * @param method add 'method' to 'currentClassName'
	 * @param curClassDefName the new created class
	 * @param newMethodName
	 * @param callOldMethod flag to call the proxied method
	 * @param returnValue the return value of new added method
	 * @param cw
	 * @param method
	 */
	public static void wrap(Class<?> serviceType, Method method, String curClassDefName, 
			String newMethodName, boolean callOldMethod, Object returnValue, ClassWriter cw) {
		String curClassDesc = "L" + curClassDefName + ";";
		String wrappedClassName = serviceType.getName();
		String wrappedClassDefName = wrappedClassName.replace('.', '/');
		String wrappedClassDesc = "L" + wrappedClassDefName + ";";
		String wrappedMethodDesc = Type.getMethodDescriptor(method);
		String wrappedMethodTypeDesc = Type.getDescriptor(method.getReturnType());
		String wrappedMethodName = method.getName();
		
		Class<?> methodReturnType;
		if (returnValue != null) {
			methodReturnType = returnValue.getClass();
		} else {
			methodReturnType = void.class;
		}
		String newMethodDesc = Type.getMethodDescriptor(
				Type.getType(methodReturnType),
				new Type[] {});
		String[] methodParams = ClassEx.getProtectedParameterNames(method);
		
		String[] paramTypeDescs = new String[methodParams.length];
		Class<?>[] paramTypes = method.getParameterTypes();
		//method.getParameterTypes();
		for (int i = 0; i < paramTypes.length; i++) {
			Class<?> clazz = paramTypes[i];
			String desc = Type.getDescriptor(clazz);
			paramTypeDescs[i] = desc;
		}
		
		//public String bizMehothod() {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, newMethodName, newMethodDesc, null, null);
		mv.visitCode();
		
		Label l1 = new Label();
		Label l3 = new Label();
		
		mv.visitLabel(l1);
		if (callOldMethod) {
			// Object bizInstance = GeneratorHelper.create(com.biz.class);
			String generatorDefName = GeneratorHelper.class.getName().replace('.', '/');
			Type type = Type.getTypeByDesc(wrappedClassDesc);
			mv.visitLdcInsn(type);
			mv.visitMethodInsn(INVOKESTATIC, generatorDefName, "create", "(Ljava/lang/Class;)Ljava/lang/Object;");
			mv.visitTypeInsn(CHECKCAST, wrappedClassDefName);
			mv.visitVarInsn(ASTORE, 1);		// var1
			
			// this.bizMethod = bizInstance.bizMethod(this.f1, this.f2);
			mv.visitLabel(l3);
			mv.visitVarInsn(ALOAD, 0);
			mv.visitVarInsn(ALOAD, 1);
			// for all method parameter
			for (int i = 0; i < methodParams.length; i++) {
				String paramName = methodParams[i];
				String paramDesc = paramTypeDescs[i];
				mv.visitVarInsn(ALOAD, 0);
				mv.visitFieldInsn(GETFIELD, curClassDefName, paramName, paramDesc);
			}
			mv.visitMethodInsn(INVOKEVIRTUAL, wrappedClassDefName, wrappedMethodName, wrappedMethodDesc);
			Class<?> returnType = method.getReturnType();
			if (!returnType.equals(void.class)) {
				mv.visitFieldInsn(PUTFIELD, curClassDefName, newMethodName, wrappedMethodTypeDesc);
			}
		}
		
		// return "success";
		Label l4 = new Label();
		mv.visitLabel(l4);
		if (returnValue != null) {
			mv.visitLdcInsn(returnValue);
			Class<?> clazz = returnValue.getClass();
			MethodVisitorHelper.visitReturn(mv, clazz.getName());
		} else {
			MethodVisitorHelper.visitReturn(mv, null);
		}
		
		Label l5 = new Label();
		mv.visitLabel(l5);
		mv.visitLocalVariable("this", curClassDesc, null, l1, l5, 0);
		if (callOldMethod) {
			mv.visitLocalVariable("bizInstance", wrappedClassDesc, null, l3, l5, 1);
		}
		mv.visitMaxs(0, 0);
		mv.visitEnd();
	}
	
}
